package com.gitee.wsl.jvm.ext.base


import com.gitee.wsl.ext.array.padStart
import com.gitee.wsl.jvm.format.encode.charHtmlEntityDecode
import com.gitee.wsl.jvm.format.encode.toHtmlEntityAll
import com.gitee.wsl.jvm.format.encode.toHtmlEntity
import com.gitee.wsl.text.regex.sample.DECIMAL_RADIX
import com.gitee.wsl.text.regex.sample.HEX_RADIX
import java.math.RoundingMode
import java.nio.charset.Charset
import java.text.DecimalFormat
import java.text.NumberFormat
import java.util.Locale
import java.util.StringJoiner
import java.util.UUID
import java.util.regex.Pattern
import java.lang.StringBuilder

val UTF8: Charset = Charsets.UTF_8
val GBK: Charset = Charset.forName("GBK")

val randomUUIDString: String
    get() = UUID.randomUUID().toString()

fun String.match(pattern: Pattern):String? {
    val matcher = pattern.matcher(this)
    return if (matcher.find()) {
        matcher.group()
    } else null
}

fun Float.toNumberString(fractionDigits: Int = 2, minIntDigits: Int = 1, isGrouping: Boolean = false, isHalfUp: Boolean = true): String =
    toDouble().toNumberString(fractionDigits, minIntDigits, isGrouping, isHalfUp)

fun Double.toNumberString(fractionDigits: Int = 2, minIntDigits: Int = 1, isGrouping: Boolean = false, isHalfUp: Boolean = true): String =
    (NumberFormat.getInstance() as DecimalFormat).apply {
        isGroupingUsed = isGrouping
        roundingMode = if (isHalfUp) RoundingMode.HALF_UP else RoundingMode.DOWN
        minimumIntegerDigits = minIntDigits
        minimumFractionDigits = fractionDigits
        maximumFractionDigits = fractionDigits
    }.format(this)

fun List<String>.join(delimiter:CharSequence):String{
    val joiner = StringJoiner(delimiter)
    forEach {
        joiner.add(it)
    }
    return joiner.toString()
}

fun Iterator<String>.join(delimiter:CharSequence):String{
    val joiner = StringJoiner(delimiter)
    this.forEach<String> {
        joiner.add(it)
    }
    return joiner.toString()
}

/*fun String.isJson(): Boolean =
    try {
        JSONObject(this)
        true
    } catch (e: Exception) {
        false
    }*/


/**
 * A matching algorithm that is similar to the searching algorithms implemented in editors such
 * as Sublime Text, TextMate, Atom and others.
 *
 *
 *
 * One point is given for every matched character. Subsequent matches yield two bonus points. A higher score
 * indicates a higher similarity.
 *
 *
 *
 *
 * This code has been adapted from Apache Commons Lang 3.3.
 *
 *
 * @since 1.0
 */
class FuzzyScore(val locale: Locale = Locale.getDefault()) {
    /**
     * Gets the locale.
     *
     * @return The locale
     */
    /**
     * Locale used to change the case of text.
     */
    //val locale: Locale

    /**
     * This returns a [Locale]-specific [FuzzyScore].
     *
     * @param locale The string matching logic is case insensitive.
     * A [Locale] is necessary to normalize both Strings to lower case.
     * @throws IllegalArgumentException
     * This is thrown if the [Locale] parameter is `null`.
     */
    /*init {
        requireNotNull(locale) { "Locale must not be null" }
        this.locale = locale
    }*/

    /**
     * Find the Fuzzy Score which indicates the similarity score between two
     * Strings.
     *
     * <pre>
     * score.fuzzyScore(null, null)                          = IllegalArgumentException
     * score.fuzzyScore("not null", null)                    = IllegalArgumentException
     * score.fuzzyScore(null, "not null")                    = IllegalArgumentException
     * score.fuzzyScore("", "")                              = 0
     * score.fuzzyScore("Workshop", "b")                     = 0
     * score.fuzzyScore("Room", "o")                         = 1
     * score.fuzzyScore("Workshop", "w")                     = 1
     * score.fuzzyScore("Workshop", "ws")                    = 2
     * score.fuzzyScore("Workshop", "wo")                    = 4
     * score.fuzzyScore("Apache Software Foundation", "asf") = 3
    </pre> *
     *
     * @param term a full term that should be matched against, must not be null
     * @param query the query that will be matched against a term, must not be
     * null
     * @return result score
     * @throws IllegalArgumentException if the term or query is `null`
     */
    fun fuzzyScore(term: CharSequence, query: CharSequence): Int {
       // require(!(term == null || query == null)) { "CharSequences must not be null" }

        // fuzzy logic is case insensitive. We normalize the Strings to lower
        // case right from the start. Turning characters to lower case
        // via Character.toLowerCase(char) is unfortunately insufficient
        // as it does not accept a locale.
        val termLowerCase = term.toString().lowercase(locale)
        val queryLowerCase = query.toString().lowercase(locale)

        // the resulting score
        var score = 0

        // the position in the term which will be scanned next for potential
        // query character matches
        var termIndex = 0

        // index of the previously matched character in the term
        var previousMatchingCharacterIndex = Int.MIN_VALUE
        for (element in queryLowerCase) {
            val queryChar = element
            var termCharacterMatchFound = false
            while (termIndex < termLowerCase.length
                && !termCharacterMatchFound
            ) {
                val termChar = termLowerCase[termIndex]
                if (queryChar == termChar) {
                    // simple character matches result in one point
                    score++

                    // subsequent character matches further improve
                    // the score.
                    if (previousMatchingCharacterIndex + 1 == termIndex) {
                        score += 2
                    }
                    previousMatchingCharacterIndex = termIndex

                    // we can leave the nested loop. Every character in the
                    // query can match at most one character in the term.
                    termCharacterMatchFound = true
                }
                termIndex++
            }
        }
        /*queryLowerCase.forEach<Char> { element->
            val queryChar = element
            var termCharacterMatchFound = false
            while (termIndex < termLowerCase.length
                && !termCharacterMatchFound
            ) {
                val termChar = termLowerCase[termIndex]
                if (queryChar == termChar) {
                    // simple character matches result in one point
                    score++

                    // subsequent character matches further improve
                    // the score.
                    if (previousMatchingCharacterIndex + 1 == termIndex) {
                        score += 2
                    }
                    previousMatchingCharacterIndex = termIndex

                    // we can leave the nested loop. Every character in the
                    // query can match at most one character in the term.
                    termCharacterMatchFound = true
                }
                termIndex++
            }
        }*/

        return score
    }
}

fun Int.toUnicodeChar(): String =
    takeIf { it < 65_536 }?.toChar()?.toString()
        ?: toBigInteger().toByteArray().padStart(4, 0x00).toString(Charsets.UTF_32BE)

fun String.unicode2String() =
    if (contains("&#")) {
        "(?i)&#x([0-9a-f]+);|&#(\\d+);"
            .toRegex()
            .findAll(this)
            .map {
                it.groupValues[1].ifEmpty { it.groupValues[2] } to
                        if (it.groupValues[0].contains("x", true)) HEX_RADIX else DECIMAL_RADIX
            }
            .fold(StringBuilder()) { acc, (c, radix) ->
                acc.append(c.toInt(radix).toUnicodeChar())
            }
            .toString()
    } else {
        split("(?i)\\\\u\\+?".toRegex())
            .drop(1)
            .fold(StringBuilder()) { acc, c ->
                val properChar = c.replace("{", "").replace("}", "")
                acc.append(properChar.toInt(HEX_RADIX).toChar())
            }
            .toString()
    }

fun String.htmlEntity2String() =
    if (contains("&")) {
        StringBuilder(this).replace("(?i)&#?(x?[0-9a-z]+)+;".toRegex()) {
            it.value.charHtmlEntityDecode()?.toUnicodeChar()
                ?: if (it.groupValues[1].startsWith("x")) {
                    it.groupValues[1].substring(1).toInt(HEX_RADIX).toUnicodeChar()
                } else {
                    it.groupValues[1].toInt().toUnicodeChar()
                }
        }
    } else {
        this
    }

/** htmlEntity编解码 */
fun String.toHtmlEntity(radix: Int = 10, isAll: Boolean = true) =
    fold(StringBuilder()) { acc, c ->
        if (isAll) {
            acc.append(c.code.toHtmlEntityAll(radix))
        } else {
            acc.append(c.code.toHtmlEntity() ?: c)
        }
    }
        .toString()

fun String.unicodeMix2String() =
    StringBuilder(this).replace(
        "(?i:\\\\u(\\+?[0-9a-f]{1,4}|\\{[0-9a-f]{1,4}})|(?i)&#x([0-9a-f]+);|&#(\\d+);)+".toRegex()
    ) {
        it.value.unicode2String()
    }
